package com.linking.redis.manager;

import com.borealis.common.utils.lock.AbstractIdLock;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

/**
 * @Author YaoWeiXin
 * @Date 2019/10/24 13:55
 * @Description Redis鎖
 */
public class RedisLock extends AbstractIdLock {

  private static final String THREAD_ID = "RedisLock:ThreadID";
  private static final long LOCK_EXPIRE = 2;
  private String lockName;
  private AbstractRedisUtils redisUtils;

  public RedisLock(String lockName, AbstractRedisUtils redisUtils) {
    this.lockName = lockName;
    this.redisUtils = redisUtils;
  }

  @Override
  protected Long getThreadId() {
    return redisUtils.hIncrement(lockName, THREAD_ID, 1L);
  }

  @Override
  protected void lock(String res, Long threadId) {
    String key = lockName + res;
    String v = "" + threadId;
    String checkValue = (String) redisUtils.get(key);
    if (v.equals(checkValue)) {
      return;
    }
    while (true) {
      boolean result = redisUtils.setIfAbsent(key, v, LOCK_EXPIRE, TimeUnit.SECONDS);
      if (result) {
        break;
      }
      try {
        Thread.sleep(1);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }

  @Override
  protected boolean returnLock(String res, Long threadId) {
    String key = lockName + res;
    String v = "" + threadId;
    String checkValue = (String) redisUtils.get(key);
    if (v.equals(checkValue)) {
      return true;
    }
    return redisUtils.setIfAbsent(key, v, LOCK_EXPIRE, TimeUnit.SECONDS);
  }

  private String unlockScriptStr = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

  private RedisScript<Boolean> unlockScript = new DefaultRedisScript<>(
      unlockScriptStr, Boolean.class);

  @Override
  protected void unlock(String res, Long threadId) {
    String key = lockName + res;
    String v = "" + threadId;
    assert v.equals(redisUtils.get(key));
    redisUtils.execute(unlockScript, Collections.singletonList(key), v);
  }

}
